package com.plugin.dao.aop;

import java.util.zip.CRC32;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shardingsphere.api.hint.HintManager;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import com.plugin.dao.annocation.ShardingStrategy;

import lombok.extern.slf4j.Slf4j;

/**
 * 分片注解aop
 */
@Slf4j
@Aspect
@Configuration
public class ShardingStrategyAop implements InitializingBean {

    @Autowired
    private ApplicationContext applicationContext;



    @Pointcut("@annotation(com.plugin.dao.annocation.ShardingStrategy)")
    public void addAdvice() {
    }

    @Around("addAdvice()")
    public Object interceptor(ProceedingJoinPoint pjp) throws Throwable {
        Signature signature = pjp.getSignature();
        Object[] args = pjp.getArgs();
        MethodSignature methodSignature = (MethodSignature) signature;
        // 通过这获取到方法的所有参数名称的字符串数组
        String[] parameterNames = methodSignature.getParameterNames();
        ShardingStrategy shardingStrategy = methodSignature.getMethod().getAnnotation(ShardingStrategy.class);
        if (StringUtils.isBlank(shardingStrategy.shardingColumn()) && StringUtils.isBlank(shardingStrategy.dbIndex())) {
            throw new IllegalArgumentException("ShardingStrategy注解shardingColumn和dbIndex不能同时为空");
        }



        String databaseName = null;
        if (StringUtils.isNotBlank(shardingStrategy.shardingColumn())) {
            Object shardingValue=getSpelValue(args,parameterNames,shardingStrategy.shardingColumn(),applicationContext);
            long mod=0;
            if (shardingValue instanceof String) {
                CRC32 crc32 = new CRC32();
                crc32.update(shardingValue.toString().getBytes());
                mod =crc32.getValue();
            } else if (shardingValue instanceof Integer || shardingValue instanceof Long) {
                mod = ((Number) shardingValue).longValue();
            }
            databaseName = "ds" + mod % 2;
            log.info("shardingColumn分片值：{}，数据库：{}", shardingValue, databaseName);
        } else {
            for (int i = 0; i < parameterNames.length; i++) {
                if (StringUtils.equals(parameterNames[i], shardingStrategy.dbIndex())) {
                    long dbIndex = (long) args[i];
                    databaseName = "ds" + dbIndex;
                    log.info("dbIndex分片值：{}，数据库：{}", dbIndex, databaseName);
                    break;
                }
            }
        }

        if (StringUtils.isBlank(databaseName)) {
            throw new IllegalArgumentException("ShardingStrategy注解属性匹配不到入参参数");
        }


        try (HintManager instance = HintManager.getInstance()) {
            instance.setDatabaseShardingValue(databaseName);
            return pjp.proceed();
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {

    }

    public static Object getSpelValue(Object[] args, String[] paraNames, String key, BeanFactory beanFactory) {


        ExpressionParser ep = new SpelExpressionParser();
        StandardEvaluationContext context = new StandardEvaluationContext();
        if (beanFactory != null) {
            context.setBeanResolver(new BeanFactoryResolver(beanFactory));
        }

        if (!ArrayUtils.isEmpty(args) && !ArrayUtils.isEmpty(paraNames)) {
            if (args.length != paraNames.length) {
                throw new IllegalArgumentException("args length must be equal to paraNames length");
            }

            for (int i = 0; i < paraNames.length; i++) {
                context.setVariable(paraNames[i], args[i]);
            }
        }

        return ep.parseExpression(key).getValue(context);
    }
}
